﻿/*******************************************************************************
Copyright (c) 2011-2013, PKStudio
Copyright (c) 2011,      Zynga Inc.
Copyright (c) 2008-2010, Ricardo Quesada

http://pkstudio.org

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*******************************************************************************/
using System;
using System.Diagnostics;
using System.Drawing;

namespace PKEngine
{
	/// <summary>
	/// PKMenu of engine.
	/// </summary>
	public class PKMenu : PKLayer, IPKRgba
	{
		#region Private Field
		private const int DefaultPriority = -256;
		private const int DefaultPadding = 5;
		private bool isClicking;
		private PKMenuItem selectedItem;
		private int realOpacity;
		private ushort realColor;
		private int realColorOpacity;
		private int priority;
		#endregion

		#region Public Properties

		public int Opacity
		{
			get
			{
				return this.realOpacity;
			}
			set
			{
				if (value < 0)
				{
					value = 0;
				}
				else if (value > 256)
				{
					value = 256;
				}
				this.DisplayedOpacity = this.realOpacity = value;

				if (this.CascadeOpacityEnabled)
				{
					int parentOpacity = 256;
					if (this.Parent != null && this.Parent is IPKRgba && ((IPKRgba)this.Parent).CascadeOpacityEnabled)
					{
						parentOpacity = ((IPKRgba)this.Parent).DisplayedOpacity;
					}
					this.UpdateDisplayedOpacity(parentOpacity);
				}
			}
		}

		public int ColorOpacity
		{
			get
			{
				return this.realColorOpacity;
			}
			set
			{
				if (value < 0)
				{
					value = 0;
				}
				else if (value > 256)
				{
					value = 256;
				}
				this.DisplayedColorOpacity = this.realColorOpacity = value;

				if (this.CascadeColorEnabled)
				{
					ushort parentColor = PKAlpha.SharedAlpha.ColorWhite;
					int parentColorOpacity = 256;
					if (this.Parent != null && this.Parent is IPKRgba && ((IPKRgba)this.Parent).CascadeColorEnabled)
					{
						parentColor = ((IPKRgba)this.Parent).DisplayedColor;
						parentColorOpacity = ((IPKRgba)this.Parent).DisplayedColorOpacity;
					}
					this.UpdateDisplayedColor(parentColor, parentColorOpacity);
				}
			}
		}

		public ushort Color
		{
			get
			{
				return this.realColor;
			}
			set
			{
				this.DisplayedColor = this.realColor = value;

				if (this.CascadeColorEnabled)
				{
					ushort parentColor = PKAlpha.SharedAlpha.ColorWhite;
					int parentColorOpacity = 256;
					if (this.Parent != null && this.Parent is IPKRgba && ((IPKRgba)this.Parent).CascadeColorEnabled)
					{
						parentColor = ((IPKRgba)this.Parent).DisplayedColor;
						parentColorOpacity = ((IPKRgba)this.Parent).DisplayedColorOpacity;
					}
					this.UpdateDisplayedColor(parentColor, parentColorOpacity);
				}
			}
		}

		/// <summary>
		/// Displayed Opacity.
		/// </summary>
		public int DisplayedOpacity { get; private set; }

		/// <summary>
		/// Displayed ColorOpacity.
		/// </summary>
		public int DisplayedColorOpacity { get; private set; }

		/// <summary>
		/// Displayed Color.
		/// </summary>
		public ushort DisplayedColor { get; private set; }

		/// <summary>
		/// Whether or not opacity should be propagated to its children.
		/// </summary>
		public bool CascadeOpacityEnabled { get; set; }

		/// <summary>
		/// Whether or not color should be propagated to its children.
		/// </summary>
		public bool CascadeColorEnabled { get; set; }

		public int Priotity
		{
			get
			{
				return this.priority;
			}
			set
			{
				this.priority = value;
				if (this.IsRunning)
				{
					PKMouseDispatcher.SharedDispatcher.SetPriority(this, value);
				}
			}
		}

		public bool IsEnabled { get; set; }
		#endregion

		#region Initialization
		public static PKMenu MenuMake()
		{
			return new PKMenu().Init();
		}

		public static PKMenu MenuMake(PKMenuItem[] menuItems)
		{
			return new PKMenu().Init(menuItems);
		}

		public PKMenu Init()
		{
			return this.Init(null);
		}

		public PKMenu Init(PKMenuItem[] menuItems)
		{
			this.DisplayedColor = this.Color = PKAlpha.SharedAlpha.ColorWhite;
			this.DisplayedOpacity = this.Opacity = this.DisplayedColorOpacity = this.ColorOpacity = 256;
			this.CascadeOpacityEnabled = this.CascadeColorEnabled = false;

			this.priority = DefaultPriority;
			this.IsMouseEnabled = true;
			this.IsEnabled = true;

			if (menuItems != null && menuItems.Length > 0)
			{
				for (int z = 0; z < menuItems.Length; z++)
				{
					this.AddChild(menuItems[z], z);
				}
			}

			this.isClicking = false;
			this.selectedItem = null;

			return this;
		}
		#endregion

		#region IPKRgba Methods
		/// <summary>
		/// Recursive method that updates display opacity.
		/// </summary>
		/// <param name="parentOpacity"></param>
		public void UpdateDisplayedOpacity(int parentOpacity)
		{
			this.DisplayedOpacity = realOpacity * parentOpacity / 256;

			if (this.CascadeOpacityEnabled)
			{
				foreach (PKNode child in this.Children)
				{
					if (child is IPKRgba)
					{
						((IPKRgba)child).UpdateDisplayedOpacity(this.DisplayedOpacity);
					}
				}
			}
		}

		/// <summary>
		/// Recursive method that updates display color.
		/// </summary>
		public void UpdateDisplayedColor(ushort parentColor, int parentColorOpacity)
		{
			this.DisplayedColorOpacity = parentColorOpacity + realColorOpacity * (256 - parentColorOpacity) / 256;
			this.DisplayedColor = PKAlpha.SharedAlpha.PixelAlpha(this.realColor, parentColor, realColorOpacity);

			if (this.CascadeColorEnabled)
			{
				foreach (PKNode child in this.Children)
				{
					if (child is IPKRgba)
					{
						((IPKRgba)child).UpdateDisplayedColor(this.DisplayedColor, this.DisplayedColorOpacity);
					}
				}
			}
		}
		#endregion

		#region Public Methods
		public override void AddChild(PKNode child, int zOrder, int tag)
		{
			Debug.Assert((child as PKMenuItem) != null, "PKMenu.AddChild: Menu only supports MenuItem objects as children.");
			base.AddChild(child, zOrder, tag);
		}

		public override void OnExit()
		{
			if (isClicking)
			{
				selectedItem.Unselect();
				isClicking = false;
				selectedItem = null;
			}

			base.OnExit();
		}

		public override bool MouseDown(PKMouseEvent mouse)
		{
			if (isClicking || !IsVisible || !IsEnabled)
			{
				return false;
			}

			for (PKNode parent = Parent; parent != null; parent = parent.Parent)
			{
				if (!parent.IsVisible)
				{
					return false;
				}
			}

			selectedItem = this.ItemForMouse(mouse);
			if (selectedItem != null)
			{
				selectedItem.Select();
				isClicking = true;
				return true;
			}
			return false;
		}

		public override void MouseDrag(PKMouseEvent mouse)
		{
			Debug.Assert(isClicking);

			PKMenuItem currentItem = this.ItemForMouse(mouse);
			if (currentItem != selectedItem)
			{
				if (selectedItem != null)
				{
					selectedItem.Unselect();
				}
				selectedItem = currentItem;
				if (selectedItem != null)
				{
					selectedItem.Select();
				}
			}
		}

		public override void MouseUp(PKMouseEvent mouse)
		{
			Debug.Assert(isClicking);

			if (selectedItem != null)
			{
				selectedItem.Activate();
			}
			isClicking = false;
		}
		#endregion

		#region Alignment
		public void AlignItemsVertically()
		{
			this.AlignItemsVerticallyWithPadding(DefaultPadding);
		}

		public void AlignItemsVerticallyWithPadding(int padding)
		{
			int y = 0;
			int maxWidth = 0;
			this.ContentSize = Size.Empty;
			if (Children != null && Children.Count > 0)
			{
				foreach (PKNode node in Children)
				{
					node.Position = new Point(0, y);
					maxWidth = node.ContentSize.Width > maxWidth ? node.ContentSize.Width : maxWidth;
					y += node.ContentSize.Height + padding;
				}
				this.ContentSize = new Size(maxWidth, y - padding);
			}
		}

		public void AlignItemsHorizontally()
		{
			this.AlignItemsHorizontallyWithPadding(DefaultPadding);
		}

		public void AlignItemsHorizontallyWithPadding(int padding)
		{
			int x = 0;
			int maxHeight = 0;
			this.ContentSize = Size.Empty;
			if (Children != null && Children.Count > 0)
			{
				foreach (PKNode node in Children)
				{
					node.Position = new Point(x, 0);
					maxHeight = node.ContentSize.Height > maxHeight ? node.ContentSize.Height : maxHeight;
					x += node.ContentSize.Width + padding;
				}
				this.ContentSize = new Size(x - padding, maxHeight);
			}
		}

		public void AlignItemsInColumns(int colunmWidth, int colunmCount)
		{
			this.AlignItemsInColumns(colunmWidth, colunmCount, DefaultPadding);
		}

		public void AlignItemsInColumns(int colunmWidth, int colunmCount, int rowSpace)
		{
			int x = 0, y = 0;
			int rowHeight = 0;
			this.ContentSize = Size.Empty;
			if (Children != null && Children.Count > 0)
			{
				foreach (PKNode node in Children)
				{
					node.Position = new Point(x * colunmWidth, y);
					rowHeight = node.ContentSize.Height > rowHeight ? node.ContentSize.Height : rowHeight;
					x++;
					if (x == colunmCount)
					{
						x = 0;
						y += rowHeight + rowSpace;
						rowHeight = 0;
					}
				}
				if (rowHeight != 0)
				{
					y += rowHeight + rowSpace;
				}
				this.ContentSize = new Size(colunmWidth * colunmCount, y - rowSpace);
			}
		}

		public void AlignItemsInRows(int rowHeight, int rowCount)
		{
			this.AlignItemsInRows(rowHeight, rowCount, DefaultPadding);
		}

		public void AlignItemsInRows(int rowHeight, int rowCount, int columnSpace)
		{
			int x = 0, y = 0;
			int columnWidth = 0;
			this.ContentSize = Size.Empty;
			if (Children != null && Children.Count > 0)
			{
				foreach (PKNode node in Children)
				{
					node.Position = new Point(x, y * rowHeight);
					columnWidth = node.ContentSize.Width > columnWidth ? node.ContentSize.Width : columnWidth;
					y++;
					if (y == rowCount)
					{
						y = 0;
						x += columnWidth + columnSpace;
						columnWidth = 0;
					}
				}
				if (columnWidth != 0)
				{
					x += columnWidth + columnSpace;
				}
				this.ContentSize = new Size(x - columnSpace, rowHeight * rowCount);
			}
		}
		#endregion

		#region Private Methods
		private PKMenuItem ItemForMouse(PKMouseEvent mouse)
		{
			if (Children != null && Children.Count > 0)
			{
				foreach (PKNode node in Children)
				{
					PKMenuItem item = node as PKMenuItem;
					if (item != null && item.IsVisible && item.IsEnabled)
					{
						if (item.BoundingBox.Contains(mouse.CurrentPos))
						{
							return item;
						}
					}
				}
			}

			return null;
		}
		#endregion
	}
}
